package com.ruoyi.business.service.wecom.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.ruoyi.business.domain.bo.GroupInfoBO;
import com.ruoyi.business.domain.bo.WeUserInfoBO;
import com.ruoyi.business.domain.koc.TbUserThirdIdentity;
import com.ruoyi.business.domain.vo.UserChatGroupStatisticVO;
import com.ruoyi.business.domain.vo.wecom.admin.RecentActiveCustomerFollowerVO;
import com.ruoyi.business.domain.vo.wecom.admin.RecentActiveCustomerVO;
import com.ruoyi.business.domain.vo.wecom.admin.RecentActiveGroupVO;
import com.ruoyi.business.domain.vo.wecom.admin.TbWecomMsgArchiveVO;
import com.ruoyi.business.domain.wecom.*;
import com.ruoyi.business.mapper.wecom.TbWecomMsgArchiveMapper;
import com.ruoyi.business.service.koc.ITbUserThirdIdentityService;
import com.ruoyi.business.service.wecom.*;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.wecom.WecomChatGroupUserTypeEnum;
import com.ruoyi.common.enums.wecom.WecomMsgActionEnum;
import com.ruoyi.common.enums.wecom.WecomMsgTypeEnum;
import com.ruoyi.common.utils.file.FileContentTypeUtils;
import com.ruoyi.common.utils.redis.RedisLock;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.factory.OssFactory;
import com.ruoyi.oss.service.IOssStrategy;
import com.tencent.wework.Finance;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpUser;
import me.chanjar.weixin.cp.bean.external.WxCpUserExternalGroupChatInfo;
import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactInfo;
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.msgaudit.WxCpChatDatas;
import me.chanjar.weixin.cp.bean.msgaudit.WxCpChatModel;
import me.chanjar.weixin.cp.constant.WxCpConsts;
import net.coobird.thumbnailator.Thumbnails;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.*;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.ruoyi.common.constant.Constants.IMAGE_COMPRESS_ENABLED;

/**
 * 企业微信会话存档表 服务实现类
 */
@Service
@Slf4j
public class TbWecomMsgArchiveServiceImpl extends ServiceImpl<TbWecomMsgArchiveMapper, TbWecomMsgArchive> implements ITbWecomMsgArchiveService {

    @Resource
    private WxCpService wxCpService;

    @Resource
    private ITbWecomExternalContactCustomerService customerService;

    @Resource
    private ITbWecomChatGroupService chatGroupService;

    @Resource
    private ITbWecomChatGroupMemberService chatGroupMemberService;

    @Resource
    private ITbWecomExternalContactFollowUserService followUserService;

    @Resource
    private RedisLock redisLock;

    @Resource
    private ITbUserThirdIdentityService tbUserThirdIdentityService;

    private final Gson gson = new Gson();


    @Override
    public TableDataInfo<TbWecomMsgArchiveVO> sysPage(String fromUserId, String toUserId, String chatId, Long beginTime, Long endTime, PageQuery pageQuery) {
        long sTime = System.currentTimeMillis();

        try {
            Page<TbWecomMsgArchiveVO> page = pageQuery.build();
            // 1. 先查询分页ID（性能关键）
            List<Long> pageIds = this.baseMapper.selectPageIdList(page.offset(), page.getSize(), fromUserId, toUserId, chatId, beginTime, endTime);
            if (pageIds.isEmpty()) {
                log.info("分页查询会话存档：未找到符合条件的记录");
                return TableDataInfo.build(new Page<>(page.getCurrent(), page.getSize(), 0));
            }
            // 2. 查询总数
            Long total = this.baseMapper.selectIdCount(fromUserId, toUserId, chatId, beginTime, endTime);
            // 3. 根据ID批量查询详情
            List<TbWecomMsgArchiveVO> records = Collections.emptyList();
            if (!pageIds.isEmpty()) {
                records = this.baseMapper.selectByIds(pageIds, fromUserId);
            }
            // 4. 构建分页结果
            Page<TbWecomMsgArchiveVO> resultPage = new Page<>(page.getCurrent(), page.getSize(), total);
            resultPage.setRecords(records);
            long eTime = System.currentTimeMillis();
            log.info("分页查询会话存档完成：总数={}, 当前页记录数={}, 耗时={}ms", total, records.size(), eTime - sTime);
            return TableDataInfo.build(resultPage);
        } catch (Exception e) {
            log.error("分页查询会话存档异常", e);
            throw new RuntimeException("查询会话存档失败", e);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handleMsgAuditNotifyEvent(WxCpXmlMessage message) {
        log.info("收到消息审计通知事件，开始处理");
        // 使用Redis分布式锁，避免集群环境下重复处理
        boolean lockAcquired = redisLock.tryLock(Constants.SYNC_LOCK_KEY, Constants.SYNC_LOCK_TIMEOUT, TimeUnit.MILLISECONDS);
        if (!lockAcquired) {
            log.info("分布式锁-->业务正在处理中--->跳过处理");
            return;
        }
        try {
            this.syncChatData();
        } catch (Exception e) {
            log.error("消息同步任务处理失败", e);
            throw e;
        } finally {
            try {
                redisLock.unlock(Constants.SYNC_LOCK_KEY);
                log.info("分布式锁已释放");
            } catch (Exception e) {
                log.warn("释放分布式锁时发生异常", e);
            }
        }
    }

    @Override
    public TableDataInfo<RecentActiveGroupVO> sysRecentActiveGroups(String chatName, String externalUserid, Long beginTime, Long endTime, PageQuery pageQuery) {
        IPage<RecentActiveGroupVO> recentActiveGroupVOIPage = this.baseMapper.sysRecentActiveGroups(pageQuery.build(), chatName, externalUserid, beginTime, endTime);
        return TableDataInfo.build(recentActiveGroupVOIPage);
    }

    @Override
    public TableDataInfo<RecentActiveCustomerVO> sysRecentActiveCustomers(String name, Long beginTime, Long endTime, PageQuery pageQuery) {
        IPage<RecentActiveCustomerVO> recentActiveCustomerVOIPage = this.baseMapper.sysRecentActiveCustomers(pageQuery.build(), name, beginTime, endTime);
        return TableDataInfo.build(recentActiveCustomerVOIPage);
    }

    @Override
    public TableDataInfo<RecentActiveCustomerFollowerVO> selectRecentServicesByCustomerId(String externalUserid, String name, Long beginTime, Long endTime, PageQuery pageQuery) {
        IPage<RecentActiveCustomerFollowerVO> recentActiveCustomerFollowerVOIPage = this.baseMapper.selectRecentServicesByCustomerId(pageQuery.build(), name, externalUserid, beginTime, endTime);
        return TableDataInfo.build(recentActiveCustomerFollowerVOIPage);
    }

    @Override
    public TableDataInfo<UserChatGroupStatisticVO> getCustomerGroupActiveStatsPage(Long userId, Long beginTime, Long endTime, PageQuery pageQuery) {
        // 根据userId 查询 客户Id 如果客户id 为空 返回空数据
        TbUserThirdIdentity tbUserThirdIdentity = tbUserThirdIdentityService.getOne(Wrappers.lambdaQuery(TbUserThirdIdentity.class)
            .eq(TbUserThirdIdentity::getUserId, userId)
            .last(Constants.MYBATIS_PLUSH_LIMIT_ONE)
        );
        if (ObjectUtil.isEmpty(tbUserThirdIdentity)) {
            return TableDataInfo.build();
        }
        TbWecomChatGroupMember tbWecomChatGroupMember = chatGroupMemberService.getOne(Wrappers.lambdaQuery(TbWecomChatGroupMember.class)
            .eq(TbWecomChatGroupMember::getUserUnionid, tbUserThirdIdentity.getIdentityUnionId())
            .orderByDesc(TbWecomChatGroupMember::getJoinTime)
            .last(Constants.MYBATIS_PLUSH_LIMIT_ONE)
        );
        if (ObjectUtil.isEmpty(tbWecomChatGroupMember)) {
            return TableDataInfo.build();
        }
        Page<UserChatGroupStatisticVO> page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize());
        IPage<UserChatGroupStatisticVO> result = this.baseMapper.selectCustomerGroupActiveStatsPage(page, tbWecomChatGroupMember.getUserId(), beginTime, endTime);
        return TableDataInfo.build(result);
    }

    /**
     * 同步聊天数据
     */
    private void syncChatData() {
        long currentSeq = getCurrentMaxSeq();
        WxCpChatDatas chatDatas = null;
        try {
            chatDatas = wxCpService.getMsgAuditService().getChatDatas(currentSeq, 50, Constants.PROXY, Constants.PASSWD, Constants.TIMEOUT);
            // 处理并保存消息
            processAndSaveMessages(chatDatas);
        } catch (Exception e) {
            log.error("同步聊天数据失败", e);
            throw new RuntimeException("同步聊天数据失败", e);
        } finally {
            // 销毁SDK
            log.info("会话处理完成 sdk:{}已销毁", chatDatas.getSdk());
            Finance.DestroySdk(chatDatas.getSdk());
        }
    }

    /**
     * 获取当前最大序列号
     */
    private long getCurrentMaxSeq() {
        return Optional.ofNullable(this.getOne(Wrappers.lambdaQuery(TbWecomMsgArchive.class).select(TbWecomMsgArchive::getSeq).orderByDesc(TbWecomMsgArchive::getSeq).last(Constants.MYBATIS_PLUSH_LIMIT_ONE))).map(TbWecomMsgArchive::getSeq).orElse(0L);
    }

    /**
     * 处理并保存消息
     */
    private void processAndSaveMessages(WxCpChatDatas chatDatas) {
        List<TbWecomMsgArchive> archiveList = new ArrayList<>();
        for (WxCpChatDatas.WxCpChatData data : chatDatas.getChatData()) {
            try {
                WxCpChatModel wxCpChatModel = decryptMessage(chatDatas.getSdk(), data);
                if (ObjectUtil.isNotEmpty(wxCpChatModel)) {
                    TbWecomMsgArchive archive = buildArchiveBaseInfo(data, wxCpChatModel);
                    processMessageContent(chatDatas.getSdk(), wxCpChatModel, archive);
                    archive.setCreateTime(new Date());
                    archiveList.add(archive);
                }
            } catch (Exception e) {
                log.error("处理消息失败, seq: {}", data.getSeq(), e);
                // 继续处理其他消息，不中断流程
            }
        }
        // 批量保存
        if (CollectionUtils.isNotEmpty(archiveList)) {
            this.saveBatch(archiveList);
            log.info("成功保存 {} 条聊天记录", archiveList.size());
        }
    }

    /**
     * 解密消息
     */
    private WxCpChatModel decryptMessage(long sdk, WxCpChatDatas.WxCpChatData data) {
        try {
            return wxCpService.getMsgAuditService().getDecryptData(sdk, data, 2);
        } catch (Exception e) {
            log.error("解密消息失败, seq: {}", data.getSeq(), e);
            throw new RuntimeException("解密消息失败", e);
        }
    }


    /**
     * 构建存档基本信息
     */
    private TbWecomMsgArchive buildArchiveBaseInfo(WxCpChatDatas.WxCpChatData data, WxCpChatModel wxCpChatModel) {
        TbWecomMsgArchive archive = new TbWecomMsgArchive();
        archive.setSeq(data.getSeq());
        archive.setMsgid(data.getMsgId());
        archive.setMsgtime(wxCpChatModel.getMsgTime());
        // 使用枚举设置动作类型
        WecomMsgActionEnum actionEnum = WecomMsgActionEnum.getEnumByCodeSafe(wxCpChatModel.getAction());
        if (ObjectUtil.isNotEmpty(actionEnum)) {
            archive.setAction(actionEnum.getCode());
        } else {
            archive.setAction(wxCpChatModel.getAction());
            log.warn("未知的动作类型: {}", wxCpChatModel.getAction());
        }
        String fromUserId = wxCpChatModel.getFrom();
        archive.setFromUserId(fromUserId);
        setUserInfo(archive, fromUserId, true);
        String roomId = wxCpChatModel.getRoomId();
        if (StringUtils.isNotEmpty(roomId)) {
            // 群聊消息
            archive.setRoomid(roomId);
            archive.setChatType(2);
            setGroupInfo(archive, roomId);
        } else {
            // 单聊消息
            archive.setChatType(1);
            String[] tolist = wxCpChatModel.getTolist();
            if (tolist != null && tolist.length > 0) {
                String toUserId = tolist[0];
                archive.setToUserId(toUserId);
                setUserInfo(archive, toUserId, false);
            }
        }
        // 使用枚举设置消息类型
        WecomMsgTypeEnum msgTypeEnum = WecomMsgTypeEnum.getEnumByCodeSafe(wxCpChatModel.getMsgType());
        if (ObjectUtil.isNotEmpty(msgTypeEnum)) {
            archive.setMsgtype(msgTypeEnum.getCode());
        } else {
            archive.setMsgtype(wxCpChatModel.getMsgType());
            log.warn("未知的消息类型: {}", wxCpChatModel.getMsgType());
        }
        return archive;
    }

    /**
     * 设置用户信息
     */
    private void setUserInfo(TbWecomMsgArchive archive, String userId, boolean isFromUser) {
        log.info("设置用户信息 userId:{}, isFromUser:{}", userId, isFromUser);
        WeUserInfoBO userInfo = getUserInfoWithCache(userId);
        if (isFromUser) {
            archive.setFromUserType(ObjectUtil.isNotEmpty(userInfo.getType()) ? userInfo.getType() : null);
            archive.setFromUserName(StrUtil.isNotEmpty(userInfo.getName()) ? userInfo.getName() : StrUtil.EMPTY);
            archive.setFromUserAvatar(StrUtil.isNotEmpty(userInfo.getAvatar()) ? userInfo.getAvatar() : StrUtil.EMPTY);
        } else {
            archive.setToUserType(ObjectUtil.isNotEmpty(userInfo.getType()) ? userInfo.getType() : null);
            archive.setToUserName(StrUtil.isNotEmpty(userInfo.getName()) ? userInfo.getName() : StrUtil.EMPTY);
            archive.setToUserAvatar(StrUtil.isNotEmpty(userInfo.getAvatar()) ? userInfo.getAvatar() : StrUtil.EMPTY);
        }
    }

    /**
     * 获取用户信息
     */
    private WeUserInfoBO getUserInfoWithCache(String userId) {
        String cacheKey = Constants.USER_INFO_CACHE_KEY + userId;
        // 先从缓存获取
        Object cacheObject = RedisUtils.getCacheObject(cacheKey);
        if (ObjectUtil.isNotEmpty(cacheObject)) {
            return BeanUtil.toBean(cacheObject, WeUserInfoBO.class);
        }
        WeUserInfoBO userInfo = fetchUserInfoFromWecom(userId);
        // 存入缓存
        RedisUtils.setCacheObject(cacheKey, userInfo, Constants.USER_INFO_CACHE_TIME, TimeUnit.HOURS);
        return userInfo;
    }

    /**
     * 从企微API获取用户信息
     */
    private WeUserInfoBO fetchUserInfoFromWecom(String userId) {
        if (userId.startsWith("wo") || userId.startsWith("wm")) {
            // 外部联系人
            return getExternalContactInfo(userId);
        } else if (userId.startsWith("wb")) {
            // 机器人，返回空信息
            return WeUserInfoBO.builder().name("机器人").avatar(StrUtil.EMPTY).type(WecomChatGroupUserTypeEnum.EXTERNAL_CONTACT.getCode()).build();
        } else {
            // 内部员工
            return getInternalUserInfo(userId);
        }
    }

    /**
     * 获取外部联系人信息
     */
    private WeUserInfoBO getExternalContactInfo(String userId) {
        // 查询数据库中的信息 查不到调用 企微api
        TbWecomExternalContactCustomer customer = customerService.getOne(Wrappers.lambdaQuery(TbWecomExternalContactCustomer.class).eq(TbWecomExternalContactCustomer::getExternalUserid, userId).orderByDesc(TbWecomExternalContactCustomer::getCreateTime).last(Constants.MYBATIS_PLUSH_LIMIT_ONE));
        // 查群成员信息
        TbWecomChatGroupMember tbWecomChatGroupMember = chatGroupMemberService.getOne(Wrappers.lambdaQuery(TbWecomChatGroupMember.class).eq(TbWecomChatGroupMember::getUserId, userId).orderByDesc(TbWecomChatGroupMember::getCreateTime).last(Constants.MYBATIS_PLUSH_LIMIT_ONE));
        if (ObjectUtil.isNotEmpty(customer)) {
            return WeUserInfoBO.builder().name(customer.getName()).avatar(customer.getAvatar()).type(WecomChatGroupUserTypeEnum.EXTERNAL_CONTACT.getCode()).build();
        }
        if (ObjectUtil.isEmpty(customer) && ObjectUtil.isNotEmpty(tbWecomChatGroupMember)) {
            return WeUserInfoBO.builder().name(tbWecomChatGroupMember.getName()).avatar(StrUtil.EMPTY).type(WecomChatGroupUserTypeEnum.EXTERNAL_CONTACT.getCode()).build();
        }
        if (ObjectUtil.isEmpty(customer) && ObjectUtil.isNotEmpty(tbWecomChatGroupMember)) {
            // 调api
            try {
                WxCpExternalContactInfo contactDetail = wxCpService.getExternalContactService().getContactDetail(userId, StrUtil.EMPTY);
                return WeUserInfoBO.builder().name(contactDetail.getExternalContact().getName()).avatar(contactDetail.getExternalContact().getAvatar()).type(WecomChatGroupUserTypeEnum.EXTERNAL_CONTACT.getCode()).build();
            } catch (WxErrorException e) {
                log.error("查询企微外部联系人信息失败, userId: {}, error: {}", userId, e.getMessage());
                return new WeUserInfoBO();
            }

        }
        return new WeUserInfoBO();
    }

    /**
     * 获取内部用户信息
     */
    private WeUserInfoBO getInternalUserInfo(String userId) {
        // 查询数据库 数据库查询不到调用api
        TbWecomExternalContactFollowUser tbWecomExternalContactFollowUser = followUserService.getOne(Wrappers.lambdaQuery(TbWecomExternalContactFollowUser.class).eq(TbWecomExternalContactFollowUser::getUserId, userId).orderByDesc(TbWecomExternalContactFollowUser::getCreateTime).last(Constants.MYBATIS_PLUSH_LIMIT_ONE));
        if (ObjectUtil.isNotEmpty(tbWecomExternalContactFollowUser)) {
            return WeUserInfoBO.builder().name(tbWecomExternalContactFollowUser.getUserName()).avatar(StrUtil.EMPTY).type(WecomChatGroupUserTypeEnum.INTERNAL_MEMBER.getCode()).build();
        } else {
            try {
                WxCpUser wxCpUser = wxCpService.getUserService().getById(userId);
                String userName = StringUtils.isNotEmpty(wxCpUser.getName()) ? wxCpUser.getName() : StrUtil.EMPTY;
                return WeUserInfoBO.builder().name(userName).avatar(StrUtil.EMPTY).type(WecomChatGroupUserTypeEnum.INTERNAL_MEMBER.getCode()).build();
            } catch (WxErrorException e) {
                log.error("查询企微内部联系人信息失败, userId: {}, error: {}", userId, e.getMessage());
                WeUserInfoBO weUserInfoBO = new WeUserInfoBO();
                weUserInfoBO.setName(userId);
                weUserInfoBO.setAvatar(StrUtil.EMPTY);
                weUserInfoBO.setType(WecomChatGroupUserTypeEnum.INTERNAL_MEMBER.getCode());
                return weUserInfoBO;
            }
        }

    }

    /**
     * 设置群聊信息
     */
    private void setGroupInfo(TbWecomMsgArchive archive, String roomId) {
        GroupInfoBO groupInfo = getGroupInfoWithCache(roomId);
        archive.setRoomName(StringUtils.isNotEmpty(groupInfo.getRoomName()) ? groupInfo.getRoomName() : StrUtil.EMPTY);
        archive.setRoomOwnerId(StringUtils.isNotEmpty(groupInfo.getRoomOwnerId()) ? groupInfo.getRoomOwnerId() : StrUtil.EMPTY);
        archive.setRoomOwnerName(StringUtils.isNotEmpty(groupInfo.getRoomOwnerName()) ? groupInfo.getRoomOwnerName() : StrUtil.EMPTY);
    }

    /**
     * 获取群聊信息
     */
    private GroupInfoBO getGroupInfoWithCache(String roomId) {
        String cacheKey = Constants.GROUP_INFO_CACHE_KEY + roomId;
        Object cacheObject = RedisUtils.getCacheObject(cacheKey);
        if (ObjectUtil.isNotEmpty(cacheObject)) {
            return BeanUtil.toBean(cacheObject, GroupInfoBO.class);
        }
        // 缓存未命中，从数据库或企微API获取
        GroupInfoBO groupInfo = getGroupInfoFromWecom(roomId);
        // 存入缓存
        RedisUtils.setCacheObject(cacheKey, groupInfo, Constants.GROUP_INFO_CACHE_TIME, TimeUnit.HOURS);
        return groupInfo;
    }


    /**
     * 从企微API获取群聊信息
     */
    private GroupInfoBO getGroupInfoFromWecom(String roomId) {
        // 先查数据库 数据库中找不到再调用api 找
        TbWecomChatGroup tbWecomChatGroup = chatGroupService.getOne(Wrappers.lambdaQuery(TbWecomChatGroup.class).eq(TbWecomChatGroup::getChatId, roomId).last(Constants.MYBATIS_PLUSH_LIMIT_ONE));
        if (ObjectUtil.isNotEmpty(tbWecomChatGroup)) {
            TbWecomExternalContactFollowUser followUser = followUserService.getOne(Wrappers.lambdaQuery(TbWecomExternalContactFollowUser.class).eq(TbWecomExternalContactFollowUser::getUserId, tbWecomChatGroup.getChatOwner()).last(Constants.MYBATIS_PLUSH_LIMIT_ONE)

            );
            return GroupInfoBO.builder().roomName(tbWecomChatGroup.getChatName()).roomOwnerId(tbWecomChatGroup.getChatOwner()).roomOwnerName(followUser.getUserName()).build();
        } else {
            try {
                WxCpUserExternalGroupChatInfo groupChat = wxCpService.getExternalContactService().getGroupChat(roomId, Constants.TRUE_1);
                String roomOwnerName = getRoomOwnerName(groupChat.getGroupChat().getOwner());
                return GroupInfoBO.builder().roomName(groupChat.getGroupChat().getName()).roomOwnerId(groupChat.getGroupChat().getOwner()).roomOwnerName(roomOwnerName).build();
            } catch (WxErrorException e) {
                log.error("从企微API获取群聊信息失败, roomId: {}, error: {}", roomId, e.getMessage());
                return new GroupInfoBO();
            }

        }

    }

    /**
     * 获取群主姓名
     */
    private String getRoomOwnerName(String ownerId) {
        if (StringUtils.isEmpty(ownerId)) {
            return StrUtil.EMPTY;
        }
        WeUserInfoBO ownerInfo = getUserInfoWithCache(ownerId);
        return ObjectUtil.isNotEmpty(ownerInfo) ? StrUtil.isNotEmpty(ownerInfo.getName()) ? ownerInfo.getName() : StrUtil.EMPTY : StrUtil.EMPTY;
    }


    /**
     * 处理消息内容
     */
    private void processMessageContent(long sdk, WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        WecomMsgTypeEnum msgTypeEnum = WecomMsgTypeEnum.getEnumByCodeSafe(wxCpChatModel.getMsgType());
        if (ObjectUtil.isEmpty(msgTypeEnum)) {
            log.warn("不支持的消息类型: {}", wxCpChatModel.getMsgType());
            return;
        }

        try {
            switch (msgTypeEnum) {
                case TEXT:
                    processTextMessage(wxCpChatModel, archive);
                    break;
                case IMAGE:
                    processImageMessage(sdk, wxCpChatModel, archive);
                    break;
                case FILE:
                    processFileMessage(sdk, wxCpChatModel, archive);
                    break;
                case EMOTION:
                    processEmotionMessage(sdk, wxCpChatModel, archive);
                    break;
                case VOICE:
                    processVoiceMessage(sdk, wxCpChatModel, archive);
                    break;
                case VIDEO:
                    processVideoMessage(sdk, wxCpChatModel, archive);
                    break;
                case REVOKE:
                    processRevokeMessage(wxCpChatModel, archive);
                    break;
                case LINK:
                    processLinkMessage(wxCpChatModel, archive);
                    break;
                case WEAPP:
                    processWeappMessage(wxCpChatModel, archive);
                    break;
                case CARD:
                    processCardMessage(wxCpChatModel, archive);
                    break;
                case NEWS:
                    processNewsMessage(wxCpChatModel, archive);
                    break;
                case LOCATION:
                    processLocationMessage(wxCpChatModel, archive);
                    break;
                case MIXED:
                    processMixedMessage(wxCpChatModel, archive);
                    break;
                default:
                    log.debug("未处理的消息类型: {}", msgTypeEnum.getDesc());
                    break;
            }
        } catch (Exception e) {
            log.error("处理消息内容失败, 消息类型: {}, 消息ID: {}", msgTypeEnum.getDesc(), archive.getMsgid(), e);
            // 不抛出异常，继续处理其他消息
        }
    }

    /**
     * 处理文本消息
     */
    private void processTextMessage(WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        if (wxCpChatModel.getText() != null) {
            archive.setTextContent(wxCpChatModel.getText().getContent());
        }
    }

    /**
     * 处理图片消息
     */
    private void processImageMessage(long sdk, WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        WxCpChatModel.Image image = wxCpChatModel.getImage();
        if (ObjectUtil.isEmpty(image)) return;
        archive.setFilesize(image.getFileSize());
        if (StringUtils.isNotEmpty(image.getSdkFileId())) {
            String ossUrl = processMediaFile(sdk, image.getSdkFileId(), archive.getMsgid(), WecomMsgTypeEnum.IMAGE.getCode(), WxCpConsts.MsgAuditMediaType.MsgAuditSuffix.JPG);
            if (StrUtil.isNotEmpty(ossUrl)) {
                archive.setOssUrl(ossUrl);
            }
        }
    }

    /**
     * 处理文件消息
     */
    private void processFileMessage(long sdk, WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        WxCpChatModel.File file = wxCpChatModel.getFile();
        if (ObjectUtil.isEmpty(file)) return;
        archive.setFilename(file.getFileName());
        archive.setFileext(file.getFileExt());
        archive.setFilesize(Long.valueOf(file.getFileSize()));
        if (StringUtils.isNotEmpty(file.getSdkFileId())) {
            String ossUrl = processMediaFile(sdk, file.getSdkFileId(), archive.getMsgid(), WecomMsgTypeEnum.FILE.getCode(), file.getFileExt());
            if (StrUtil.isNotEmpty(ossUrl)) {
                archive.setOssUrl(ossUrl);
            }
        }
    }

    /**
     * 处理表情消息
     */
    private void processEmotionMessage(long sdk, WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        WxCpChatModel.Emotion emotion = wxCpChatModel.getEmotion();
        if (ObjectUtil.isEmpty(emotion)) return;
        archive.setEmotionType(emotion.getType());
        archive.setEmotionWidth(emotion.getWidth());
        archive.setEmotionHeight(emotion.getHeight());
        archive.setTitle(emotion.getTitle());
        archive.setFilesize(Long.valueOf(emotion.getImageSize()));
        if (StringUtils.isNotEmpty(emotion.getSdkFileId())) {
            String suffix = emotion.getType() == 1 ? WxCpConsts.MsgAuditMediaType.MsgAuditSuffix.GIF : WxCpConsts.MsgAuditMediaType.MsgAuditSuffix.PNG;
            String ossUrl = processMediaFile(sdk, emotion.getSdkFileId(), archive.getMsgid(), WecomMsgTypeEnum.EMOTION.getCode(), suffix);
            if (StringUtils.isNotEmpty(ossUrl)) {
                archive.setOssUrl(ossUrl);
            }
        }
    }

    /**
     * 处理语音消息
     */
    private void processVoiceMessage(long sdk, WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        WxCpChatModel.Voice voice = wxCpChatModel.getVoice();
        if (ObjectUtil.isEmpty(voice)) return;
        archive.setFilesize(voice.getVoiceSize());
        archive.setPlayLength(voice.getPlayLength());
        if (StringUtils.isNotEmpty(voice.getSdkFileId())) {
            String ossUrl = processMediaFile(sdk, voice.getSdkFileId(), archive.getMsgid(), WecomMsgTypeEnum.VOICE.getCode(), WxCpConsts.MsgAuditMediaType.MsgAuditSuffix.AMR);
            if (StrUtil.isNotEmpty(ossUrl)) {
                archive.setOssUrl(ossUrl);
            }
        }
    }

    /**
     * 处理视频消息
     */
    private void processVideoMessage(long sdk, WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        WxCpChatModel.Video video = wxCpChatModel.getVideo();
        if (ObjectUtil.isEmpty(video)) return;
        archive.setFilesize(video.getFileSize());
        archive.setPlayLength(video.getPlayLength());
        if (StringUtils.isNotEmpty(video.getSdkFileId())) {
            String ossUrl = processMediaFile(sdk, video.getSdkFileId(), archive.getMsgid(), WecomMsgTypeEnum.VIDEO.getCode(), WxCpConsts.MsgAuditMediaType.MsgAuditSuffix.MP4);
            if (StrUtil.isNotEmpty(ossUrl)) {
                archive.setOssUrl(ossUrl);
            }
        }
    }

    /**
     * 处理撤回消息
     */
    private void processRevokeMessage(WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        WxCpChatModel.Revoke revoke = wxCpChatModel.getRevoke();
        if (ObjectUtil.isEmpty(revoke)) return;
        archive.setPreMsgId(revoke.getPreMsgId());
    }

    /**
     * 处理链接消息
     */
    private void processLinkMessage(WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        WxCpChatModel.Link link = wxCpChatModel.getLink();
        if (ObjectUtil.isEmpty(link)) return;
        archive.setTitle(link.getTitle());
        archive.setDescription(link.getDescription());
        archive.setLinkUrl(link.getLinkUrl());
        archive.setImageUrl(link.getImageUrl());
    }

    /**
     * 处理小程序消息
     */
    private void processWeappMessage(WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        WxCpChatModel.Weapp weapp = wxCpChatModel.getWeapp();
        if (ObjectUtil.isEmpty(weapp)) return;
        archive.setTitle(weapp.getTitle());
        archive.setDescription(weapp.getDescription());
        archive.setUserName(weapp.getUserName());
        archive.setDisplayName(weapp.getDisplayName());
    }

    /**
     * 处理名片消息
     */
    private void processCardMessage(WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        WxCpChatModel.Card card = wxCpChatModel.getCard();
        if (ObjectUtil.isEmpty(card)) return;
        archive.setCardCorpName(card.getCorpName());
        archive.setCardUserId(card.getUserId());
    }

    /**
     * 处理图文消息
     */
    private void processNewsMessage(WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        WxCpChatModel.Info info = wxCpChatModel.getInfo();
        if (ObjectUtil.isNotEmpty(info) && ObjectUtil.isNotEmpty(info.getNewsItem())) {
            archive.setNewsInfo(gson.toJson(info.getNewsItem()));
        }
    }

    /**
     * 处理混合消息
     */
    private void processMixedMessage(WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        WxCpChatModel.Mixed mixed = wxCpChatModel.getMixed();
        if (ObjectUtil.isNotEmpty(mixed) && ObjectUtil.isNotEmpty(mixed.getItem())) {
            archive.setMixedInfo(gson.toJson(mixed.getItem()));
        }
    }

    /**
     * 处理位置消息
     */
    private void processLocationMessage(WxCpChatModel wxCpChatModel, TbWecomMsgArchive archive) {
        WxCpChatModel.Location location = wxCpChatModel.getLocation();
        if (ObjectUtil.isEmpty(location)) return;
        archive.setLatitude(BigDecimal.valueOf(location.getLatitude()));
        archive.setLongitude(BigDecimal.valueOf(location.getLongitude()));
        archive.setTitle(location.getTitle());
        archive.setAddress(location.getAddress());
        archive.setZoom(location.getZoom());
    }

    /**
     * 处理文件下载 - 修复文件压缩导致文件不存在的问题
     */
    private String processMediaFile(long sdk, String sdkFileId, String msgId, String fileType, String fileExtension) {
        String cacheKey = Constants.MEDIA_FILE_CACHE_KEY + sdkFileId;
        // 1. 先从缓存获取OSS URL
        String cachedOssUrl = RedisUtils.getCacheObject(cacheKey);
        if (StrUtil.isNotEmpty(cachedOssUrl)) {
            return cachedOssUrl;
        }
        // 添加分布式锁，防止并发重复处理同一个文件
        String lockKey = "MEDIA_PROCESS_LOCK:" + sdkFileId;
        boolean lockAcquired = false;
        File tempFile = null;
        File compressedFile = null; // 单独管理压缩文件
        try {
            // 尝试获取分布式锁，超时时间30秒
            lockAcquired = redisLock.tryLock(lockKey, 30, TimeUnit.SECONDS);
            if (!lockAcquired) {
                log.debug("其他线程正在处理该文件，等待重试, sdkFileId: {}", sdkFileId);
                // 等待一段时间后再次检查缓存
                Thread.sleep(1000);
                cachedOssUrl = RedisUtils.getCacheObject(cacheKey);
                if (StrUtil.isNotEmpty(cachedOssUrl)) {
                    return cachedOssUrl;
                }
                return null;
            }

            // 2. 流式下载到临时文件
            tempFile = downloadMediaFileToTemp(sdk, msgId, fileExtension, sdkFileId);
            if (tempFile == null || !tempFile.exists() || tempFile.length() == 0) {
                log.error("下载媒体文件失败或文件为空, sdkFileId: {}, 文件存在: {}, 文件大小: {}", sdkFileId, tempFile != null && tempFile.exists(), tempFile != null ? tempFile.length() : 0);
                return null;
            }
            File fileToUpload = tempFile;
            // 3. 图片压缩（如果启用且是图片文件）
            if (IMAGE_COMPRESS_ENABLED && isImageFile(fileType)) {
                compressedFile = compressImageFile(tempFile, fileExtension);
                if (compressedFile != null && compressedFile.exists() && compressedFile.length() > 0) {
                    // 验证压缩文件有效性
                    if (isFileValid(compressedFile)) {
                        log.info("使用压缩后的文件, 原大小: {} bytes, 压缩后: {} bytes", tempFile.length(), compressedFile.length());
                        fileToUpload = compressedFile;
                        // 注意：这里不立即删除原文件，等到最后统一清理
                    } else {
                        log.error("压缩文件无效，使用原文件");
                        deleteTempFileSafely(compressedFile);
                        compressedFile = null;
                        fileToUpload = tempFile;
                    }
                } else {
                    log.info("使用原文件上传");
                    fileToUpload = tempFile;
                }
            }
            // 4. 再次确认文件有效性
            if (fileToUpload == null || !fileToUpload.exists() || fileToUpload.length() == 0) {
                log.error("上传文件不存在或为空, sdkFileId: {}, 文件: {}, 存在: {}, 大小: {}", sdkFileId, fileToUpload != null ? fileToUpload.getAbsolutePath() : "null", fileToUpload != null && fileToUpload.exists(), fileToUpload != null ? fileToUpload.length() : 0);
                return null;
            }

            // 5. 流式上传到OSS
            String ossUrl = uploadFileToOss(fileToUpload, fileExtension);
            if (StrUtil.isNotEmpty(ossUrl)) {
                // 存入缓存
                RedisUtils.setCacheObject(cacheKey, ossUrl, Constants.MEDIA_FILE_CACHE_TIME, TimeUnit.DAYS);
                log.info("媒体文件处理完成并已缓存, sdkFileId: {}, 文件大小: {} bytes, OSS URL: {}", sdkFileId, fileToUpload.length(), ossUrl);
                return ossUrl;
            } else {
                log.error("上传到OSS失败, sdkFileId: {}", sdkFileId);
            }
        } catch (Exception e) {
            log.error("处理媒体文件失败, sdkFileId: {}", sdkFileId, e);
        } finally {
            // 释放分布式锁
            if (lockAcquired) {
                redisLock.unlock(lockKey);
            }
            // 6. 确保临时文件被删除 - 按顺序安全删除
            deleteTempFileSafely(compressedFile); // 先删除压缩文件
            deleteTempFileSafely(tempFile);       // 再删除原文件
        }
        return null;
    }

    /**
     * 修复的文件压缩方法 - 避免文件不存在问题
     */
    private File compressImageFile(File originalFile, String fileExtension) {
        if (originalFile == null || !originalFile.exists()) {
            log.error("压缩原文件不存在");
            return null;
        }

        // 验证原文件是否可读
        if (!originalFile.canRead() || originalFile.length() == 0) {
            log.error("压缩原文件不可读或为空: {}", originalFile.getAbsolutePath());
            return null;
        }

        // 小文件不压缩
        if (originalFile.length() < 1024 * 100) {
            log.info("图片文件过小，跳过压缩: {} bytes", originalFile.length());
            return null; // 返回null表示不压缩，而不是返回原文件
        }

        File compressedFile = null;

        try {
            // 创建压缩文件 - 使用更安全的方式
            compressedFile = File.createTempFile("compressed_", fileExtension);
            log.info("创建压缩临时文件: {}", compressedFile.getAbsolutePath());

            // 验证压缩文件是否创建成功
            if (!compressedFile.exists() || !compressedFile.canWrite()) {
                log.error("压缩文件创建失败或不可写: {}", compressedFile.getAbsolutePath());
                return null;
            }

            log.info("开始图片压缩, 原文件: {} bytes, 目标: {}", originalFile.length(), compressedFile.getAbsolutePath());

            // 使用更安全的压缩方式，添加异常处理
            Thumbnails.of(originalFile).size(Constants.MAX_IMAGE_WIDTH, Constants.MAX_IMAGE_HEIGHT).outputFormat(getFormatName(fileExtension)).outputQuality(Constants.IMAGE_COMPRESS_QUALITY).toFile(compressedFile);

            // 等待文件写入完成
            Thread.sleep(100);

            // 验证压缩结果
            if (!compressedFile.exists()) {
                log.error("压缩后文件不存在");
                return null;
            }

            if (compressedFile.length() == 0) {
                log.error("压缩后文件为空");
                deleteTempFileSafely(compressedFile);
                return null;
            }

            // 检查压缩效果 - 如果压缩后文件更大，则不使用压缩文件
            if (compressedFile.length() >= originalFile.length()) {
                log.info("压缩后文件变大，放弃压缩。原大小: {}KB, 压缩后: {}KB", originalFile.length() / 1024, compressedFile.length() / 1024);
                deleteTempFileSafely(compressedFile);
                return null;
            }

            // 验证压缩文件是否可读
            if (!compressedFile.canRead()) {
                log.error("压缩文件不可读: {}", compressedFile.getAbsolutePath());
                deleteTempFileSafely(compressedFile);
                return null;
            }

            log.info("图片压缩成功。原大小: {}KB, 压缩后: {}KB, 压缩率: {}%", originalFile.length() / 1024, compressedFile.length() / 1024, (originalFile.length() - compressedFile.length()) * 100 / originalFile.length());

            return compressedFile;

        } catch (Exception e) {
            log.error("图片压缩失败, 文件: {}", originalFile.getAbsolutePath(), e);
            deleteTempFileSafely(compressedFile);
            return null;
        }
    }

    /**
     * 文件有效性验证方法
     */
    private boolean isFileValid(File file) {
        if (file == null) {
            return false;
        }

        if (!file.exists()) {
            log.error("文件不存在: {}", file.getAbsolutePath());
            return false;
        }

        if (!file.isFile()) {
            log.error("不是文件: {}", file.getAbsolutePath());
            return false;
        }

        if (file.length() == 0) {
            log.error("文件大小为0: {}", file.getAbsolutePath());
            return false;
        }

        if (!file.canRead()) {
            log.error("文件不可读: {}", file.getAbsolutePath());
            return false;
        }

        return true;
    }

    /**
     * 增强的下载方法 - 确保文件创建成功
     */
    private File downloadMediaFileToTemp(long sdk, String msgId, String fileExtension, String sdkFileId) {
        File tempFile = null;
        final int MAX_RETRY_COUNT = 3;
        int retryCount = 0;

        while (retryCount < MAX_RETRY_COUNT) {
            try {
                tempFile = File.createTempFile("media_" + msgId + "_", fileExtension);
                // 验证文件是否创建成功
                if (!tempFile.exists() || !tempFile.canWrite()) {
                    log.error("临时文件创建失败, sdkFileId: {}", sdkFileId);
                    retryCount++;
                    continue;
                }
                log.info("创建临时文件成功: {}, sdkFileId: {}", tempFile.getAbsolutePath(), sdkFileId);
                // 其余下载逻辑保持不变...
                try (FileOutputStream fos = new FileOutputStream(tempFile, true)) {
                    String indexbuf = "";
                    boolean downloadSuccess = false;
                    long totalBytes = 0;

                    while (!downloadSuccess && retryCount < MAX_RETRY_COUNT) {
                        long mediaData = Finance.NewMediaData();
                        try {
                            long ret = Finance.GetMediaData(sdk, indexbuf, sdkFileId, "", "", 1000, mediaData);
                            if (ret != 0) {
                                log.error("下载媒体文件分片失败, ret: {}, sdkFileId: {}, 重试次数: {}", ret, sdkFileId, retryCount);
                                retryCount++;
                                if (retryCount >= MAX_RETRY_COUNT) {
                                    break;
                                }
                                // 重试前等待
                                Thread.sleep(1000 * retryCount);
                                continue;
                            }
                            // 流式写入文件
                            byte[] chunkData = Finance.GetData(mediaData);
                            if (chunkData != null && chunkData.length > 0) {
                                fos.write(chunkData);
                                totalBytes += chunkData.length;
                                fos.flush(); // 确保数据写入磁盘
                                log.debug("写入分片数据, 大小: {} bytes, 累计: {} bytes", chunkData.length, totalBytes);
                            } else {
                                log.warn("分片数据为空, sdkFileId: {}", sdkFileId);
                            }
                            if (Finance.IsMediaDataFinish(mediaData) == 1) {
                                downloadSuccess = true;
                                log.info("媒体文件下载完成, sdkFileId: {}, 文件大小: {} bytes", sdkFileId, totalBytes);
                                break;
                            } else {
                                indexbuf = Finance.GetOutIndexBuf(mediaData);
                            }
                        } finally {
                            Finance.FreeMediaData(mediaData);
                        }
                    }

                    if (downloadSuccess && isFileValid(tempFile)) {
                        return tempFile;
                    } else {
                        log.error("下载未完成或文件无效, sdkFileId: {}, 下载成功: {}, 文件大小: {}", sdkFileId, downloadSuccess, tempFile.length());
                        break;
                    }
                }
            } catch (Exception e) {
                log.error("下载媒体文件到临时文件失败, sdkFileId: {}, 重试次数: {}", sdkFileId, retryCount, e);
                retryCount++;
                deleteTempFileSafely(tempFile);
                tempFile = null;

                if (retryCount < MAX_RETRY_COUNT) {
                    try {
                        Thread.sleep(1000 * retryCount);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
        }
        log.error("下载媒体文件失败，已达最大重试次数, sdkFileId: {}", sdkFileId);
        deleteTempFileSafely(tempFile);
        return null;
    }

    /**
     * 增强的文件上传方法
     */
    private String uploadFileToOss(File file, String fileExtension) {
        if (file == null || !file.exists()) {
            log.error("上传文件不存在, file: {}", file != null ? file.getAbsolutePath() : "null");
            return null;
        }

        if (file.length() == 0) {
            log.error("上传文件为空, 文件路径: {}, 文件大小: 0", file.getAbsolutePath());
            return null;
        }
        try (FileInputStream fileStream = new FileInputStream(file)) {
            IOssStrategy storage = OssFactory.instance();
            UploadResult uploadResult = storage.uploadSuffix(fileStream, fileExtension, FileContentTypeUtils.getContentType(fileExtension));

            if (uploadResult != null && StringUtils.isNotEmpty(uploadResult.getUrl())) {
                log.info("文件上传OSS成功, 文件大小: {} bytes, OSS URL: {}", file.length(), uploadResult.getUrl());
                return uploadResult.getUrl();
            } else {
                log.error("OSS上传返回结果为空");
            }
        } catch (FileNotFoundException e) {
            log.error("文件不存在，可能已被删除: {}", file.getAbsolutePath(), e);
        } catch (Exception e) {
            log.error("上传文件到OSS失败, 文件路径: {}, 文件大小: {}", file.getAbsolutePath(), file.length(), e);
        }
        return null;
    }


    /**
     * 安全的临时文件删除
     */
    private void deleteTempFileSafely(File tempFile) {
        if (tempFile != null && tempFile.exists()) {
            try {
                Files.delete(tempFile.toPath());
                log.debug("临时文件已删除: {}", tempFile.getAbsolutePath());
            } catch (IOException e) {
                log.warn("删除临时文件失败: {}", tempFile.getAbsolutePath(), e);
                // 尝试在JVM退出时删除
                tempFile.deleteOnExit();
            }
        }
    }

    /**
     * 根据文件扩展名获取格式名
     */
    private String getFormatName(String fileExtension) {
        if (StrUtil.isEmpty(fileExtension)) return "png";
        switch (fileExtension.toLowerCase()) {
            case ".jpg":
            case ".jpeg":
                return "jpeg";
            case ".png":
                return "png";
            case ".gif":
                return "gif";
            case ".bmp":
                return "bmp";
            default:
                return "png";
        }
    }


    /**
     * 判断是否为图片文件
     */
    private boolean isImageFile(String fileType) {
        return "image".equals(fileType) || "emotion".equals(fileType);
    }


}
